<?php
/**
 * Formtastic: A simple PHP HTML form generation and validation library
 *  
 * Provides a simple way to define, render and validate HTML forms.
 *
 * This code is liscensed under the GPLv3.
 * 
 * @author Tim Stackhouse
 * @version $Id$
 * @package formtastic
 **/


/**
 * Form class
 *
 * Base class for the formtastic library, only class that should be created
 * with the new operator.
 *
 * @package formtastic
 * @author Tim Stackhouse
 **/
class Form {
  var $fields;
  var $name;
  var $method;
  var $action;
  var $error;
  
  //build form object
  function __construct() {
    $this->fields = Array();
    $this->name = "myform";
    $this->method = "POST";
    $this->action = "";
  }

  //== Setter/Getter methods =================================================

  //setter getter for fields
  function fields($fields = NULL) {
    if($fields != NULL) {
      return $this->fields = $fields;
    }
    else {
      return $this->fields;
    }
  }
  
  //setter getter for name
  function name($name = NULL) {
    if($name != NULL) {
      return $this->name = $name;
    }
    else {
      return $this->name;
    }
  }
  
  //setter getter for method
  function method($method = NULL) {
    if($method != NULL) {
      return $this->method = $method;
    }
    else {
      return $this->method;
    }
  }
  
  //setter getter for action
  function action($action = NULL) {
    if($action != NULL) {
      return $this->action = $action;
    }
    else {
      return $this->action;
    }
  }
  
  //== End Setter/Getter methods =============================================
  
  /**
   * addField
   *
   * Take a Field object and add it to the form
   * 
   * @param Field $field The field object to add to the form
   * @return This form object for method chaining
   * @author Tim Stackhouse
   **/
  function addField($field) {
    //sanity check to make sure we're getting a field object
    if(is_object($field) && is_subclass_of($field, "Field")) {
      $this->fields[] = $field;
    }
    return $this;
  }
  
  /**
   * populate
   *
   * Accept an array of form data (typically from $_POST) to use to populate
   * the field objects of this form.  Should be called after all fields are
   * declared and before rendering.
   * 
   * @param Array $data An array of form data to populate from
   * @return void
   * @author Tim Stackhouse
   */
  function populate($data) {
    //$data = scrub($data); //can't scrub data, screws with checkboxes
        
    foreach($this->fields as $field) {
      if(isset($data[$field->name])) {
        $field->value($data[$field->name]);
      }
    }
  }
  
  /**
   * validate
   * 
   * Loop over the form elements and call the validate function of each
   * perform an AND on the results & return.
   *
   * @return boolean Wether or not this form passed validation.
   * @author Tim Stackhouse
   */
  function validate() {
    $valid = true;
    
    foreach($this->fields as $field) {
      $valid &= $field->validate();
    }
    
    if(!$valid) {
      $this->error = '<p class="error">You had one or more errors in your submission</p>';
    }
    
    return $valid;
  }
  
  function process() {
    
  }
  
  /**
   * render
   * 
   * Return the HTML representation of this form and all its fields.
   *
   * @return string The HTML representation of this form
   * @author Tim Stackhouse
   */
  function render() {
    $output = sprintf('<form name="%s" method="%s" action="%s">%s',
                      $this->name,
                      $this->method,
                      $this->action,
                      $this->error);
    
    foreach ($this->fields as $field) {
      $output .= $field->render();
    }
    
    $output .= '<p><input type="submit" /></p></form>';
    
    return $output;
  }
  
  /**
   * display
   * 
   * Return an HTML representation of this form's values, useful for debugging and displaying submitted data
   *
   * @return string The HTML representation of this form's data
   * @author Tim Stackhouse
   */
  function display() {
    $output = sprintf('<h1>Form "%s"</h1>',
                      $this->name);
    
    foreach ($this->fields as $field) {
      $output .= $field->display();
    }
        
    return $output;
  }
} //END Form


//FieldFactory Class
class FieldFactory {
  function TextField() {
    $field = new TextField();
    $field->required(false);
    
    return $field;
  }
  
  function TextAreaField() {
    $field = new TextAreaField();
    $field->required(false);
    
    return $field;
  }
  
  function RadioButtonField() {
    $field = new RadioButtonField();
    $field->required(false);
    
    return $field;
  }
  
  function CheckboxField() {
    $field = new CheckboxField();
    $field->required(false);
    $field->value(Array());
    
    return $field;
  }
}

//Field Class
class Field {
  var $name;
  var $value;
  var $label;
  var $description;
  var $required;
  var $error;
  
  function __construct() {
    
  }
  
  //== Setter/Getters for Fields =============================================
  
  //setter getter for name
  function name($name = NULL) {
    if($name != NULL) {
      $this->name = $name;
      return $this;
    }
    else {
      return $this->name;
    }
  }
  
  //setter getter for value
  function value($value = NULL) {
    if($value != NULL) {
      $this->value = $value;
      return $this;
    }
    else {
      return $this->value;
    }
  }
  
  //setter getter for label
  function label($label = NULL) {
    if($label != NULL) {
      $this->label = $label;
      return $this;
    }
    else {
      return $this->label;
    }
  }
  
  //setter getter for description
  function description($description = NULL) {
    if($description != NULL) {
      $this->description = $description;
      return $this;
    }
    else {
      return $this->description;
    }
  }
  
  //setter getter for required
  function required($required = NULL) {
    if($required != NULL) {
      $this->required = $required;
      return $this;
    }
    else {
      return $this->required;
    }
  }
  
  //== End Setter/Getters for Fields =========================================
  
  function render() {
    
  }
  
  function display() {
    
  }
  
  function validate() {
    if($this->required) {
      if($this->value == "") {
        $this->error = "error";
        return false;
      }
    }
    return true;
  }
}

class TextField extends Field {
  function render() {
    //There has to be a better way to do this than massive sorintf statements
    return sprintf('<p class="%s"><label for="formtastic-%s">%s: <input type="text" id="formtastic-%s" name="%s" value="%s" /> %s</label> %s</p>',
                   $this->error,
                   $this->name,
                   $this->label,
                   $this->name,
                   $this->name,
                   $this->value,
                   ($this->required ? '*' : ''),
                   $this->description);
  }
  
  function display() {
    return sprintf('<p>%s: %s</p>',
                   $this->label,
                   $this->value);
  }
}

class TextAreaField extends Field {
  function render() {
    //There has to be a better way to do this than massive sorintf statements
    return sprintf('<p class="%s"><label for="formtastic-%s">%s %s</label><br />%s<textarea id="formtastic-%s"  name="%s">%s</textarea></p>',
                   $this->error,
                   $this->name,
                   $this->label,
                   ($this->required ? '*' : ''),
                   (($this->description != "") ? $this->description . "<br />" : ''),
                   $this->name,
                   $this->name,
                   $this->value);
  }
  
  function display() {
    return sprintf('<p>%s: <br />%s</p>',
                   $this->label,
                   $this->value);
  }
}

class DefinedValuesField extends Field {
  var $values;

  //setter getter for values
  function values($values = NULL) {
    if($values != NULL) {
      $this->values = $values;
      return $this;
    }
    else {
      return $this->values;
    }
  }
}

class RadioButtonField extends DefinedValuesField {  
  function render() {
    $output = sprintf('<p class="%s"><strong>%s %s</strong><br />%s',
                   $this->error,
                   $this->label,
                   ($this->required ? '*' : ''),
                   (($this->description != "") ? $this->description . "<br />" : ''));
    
    foreach($this->values as $value => $data) {
      $checked = ($this->value == $value) ? 'checked="checked" ' : '';
      $label = (is_array($data) && array_key_exists('label', $data)) ? $data['label'] : $data;
      $description = (is_array($data) && array_key_exists('description', $data)) ? $data['description'] : '';
      $output .= <<<EOF
                        <label for="formtastic-{$this->name}-{$value}">
                          <input type="radio"
                                 id="formtastic-{$this->name}-{$value}"
                                 name="{$this->name}"
                                 value="{$value}"
                                 {$checked}/>
                          {$label}
                        </label> {$description}<br />
EOF;
      //I still think this is less than ideal... PHP's heredoc requires the terminator to not be indented... -TS
      //There has to be a better way to do this than massive sorintf statements  
/*      $output .= sprintf('<label for="formtastic-%s-%s"><input type="radio" id="formtastic-%s-%s" name="%s" value="%s" %s/> %s</label> %s<br />',
                         $this->name, $value,
                         $this->name, $value,
                         $this->name,
                         $value,
                         (($this->value == $value) ? 'checked="checked" ' : ''),
                         ((is_array($data) && array_key_exists('label', $data))? $data['label'] : $data),
                         ((is_array($data) && array_key_exists('description', $data))? $data['description'] : ''));*/
    }
    
    $output .= "</p>";
    
    return $output;
  }
  
  function display() {
    return sprintf('<p>%s: <br />%s: %s</p>',
                   $this->label,
                   $this->value,
                   $this->values[$this->value]);
  }
}

class CheckboxField extends DefinedValuesField {
  function render() {  
    $output = sprintf('<p class="%s"><strong>%s %s</strong><br />%s',
                      $this->error,
                      $this->label,
                      ($this->required ? '*' : ''),
                      (($this->description != "") ? $this->description . "<br />" : ''));
        
    foreach($this->values as $value => $data) {
    //There has to be a better way to do this than massive sorintf statements
      $output .= sprintf('<label for="formtastic-%s-%s"><input type="checkbox" id="formtastic-%s-%s" name="%s[]" value="%s" %s/> %s</label> %s<br />',
                         $this->name, $value,
                         $this->name, $value,
                         $this->name,
                         $value,
                         ((is_array($this->value) && in_array($value, $this->value)) ? 'checked="checked" ' : ''),
                         ((is_array($data) && array_key_exists('label', $data))? $data['label'] : $data),
                         ((is_array($data) && array_key_exists('description', $data))? $data['description'] : ''));
    }
    
    $output .= "</p>";
    
    return $output;
  }
  
  function display() {  
    $output = sprintf('<p>%s: <br />',
                   $this->label);
    
    foreach($this->value as $value) {
      $output .= sprintf('%s: %s<br />',
                     $value,
                     ((is_array($this->values[$value]) && array_key_exists('label', $this->values[$value]))? $this->values[$value]['label'] : $this->values[$value]));
    }
                 
    $output .= "</p>";
          
    return $output;
  }
}

//helper functions ===========================================================

function scrub($data) {
  //crazy CYA scrubbing
  return preg_replace('/[^A-Za-z0-9@\s\.,\-]/', '', $data);
}
